package com.modestmaps.extras
{
    import com.modestmaps.Map;
    import com.modestmaps.core.PolylineMarker;
    import com.modestmaps.events.OverlayEvent;
    import com.modestmaps.geo.Location;
    
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
	[Event(name="overlayClicked",type="com.modestmaps.events.OverlayEvent")]

    /** 
    * Draws a vector polygon overlay on the map. 
	* <p>This class is an extension to the modestmaps mapping API version 1.0 for Actionscript 3</p>
    * <p>The polygon is defined by a list of Location objects (com.modestmaps.geo.Location) 
    * containing lat/lon values that specify points on the map.
    * </p>
    * <p>Each PolylineOverlay can have a custom line style, background fill color and background
    * alpha specified. It can be made clickable so it listens for mouse events and dispatches  
    * an OverlayEvent.OVERLAY_CLICKED event when the mouse is clicked. 
    * </p>
    * <p>The PolylineOverlay also can have an optional marker consisting of
    * a title and subtitle, placed on the map at the specified centroid Location.</p>
	*
	* @see http://www.modestmaps.com the official ModestMaps site
	* @see Overlay
	 * @see com.modestmaps.events.OverlayEvent OverlayEvent
	 * @see com.modestmaps.core.PolylineMarker PolylineMarker
	 * @see com.modestmaps.Map Map
    */
    public class PolylineOverlay extends Overlay
    {
    	/** Minimum delta (pixels) required to consider mouse gesture a "drag" vs "click" */
    	public static const CLICK_TOLERANCE:int = 5; 
		/** LineStyle for the polygon border -- default is black, 2 pixels thick */
        public var lineStyle:LineStyle = new LineStyle(2,0x000000);
        /** polygon's fill color, default is red */
        public var fillColor:uint = 0xFF0000;
		/** polygon's fill alpha, default 0.3 */
        public var fillAlpha:Number=0.3;
		/** polygon's marker (for labeling the polygon) */
        public var marker:PolylineMarker;
		/** centroid location of the polygon, for plotting marker */
        public var centroid:Location;

		// an array of com.modestmaps.geo.Location objects (lat/lon pairs) describing the polygon
		private var points:Array = [];
		// keep track of mouse coordinates at start of drag
        private var startMouseX:int;
        private var startMouseY:int;
        // var for compupting polygon's visible dimensions (used in sizing and positioning labels)
        private var left:int;
        private var right:int;
        private var top:int;
        private var bottom:int;
        private var _stageRect:Rectangle = new Rectangle();
    
		/**
		 * Construct a new instance of PolylineOverlay, attached to the specified
		 * map and having the specified groupID. 
		 * @param map is a handle on the map for this overlay. 
		 * @param groupID is a string that identifies this overlay as part of a group,
		 * enabling the OverlayManager to perform group operations (default = empty string).
		 */
        public function PolylineOverlay(map:Map, groupID:String="")
        {
            super(map,groupID);
        }
        		
		/** 
		 * Adds the set of points that describe the polygon. Any existing points are overwritten.
		 * @param poly is an array of com.modestmaps.geo.Location objects representing a set of
		 * lat/lon points for the polygon to be drawn on the map. 
		 */
        public function addPolyline(poly:Array):void
        {
			// clear the local array of points
        	points.length = 0;
			// copy the polygon points to local array
			for each (var l:Location in poly) {
				points.push(l);
			}
        }

		/**
		 * Creates a new PolylineMarker object with the specified title and subTitle 
		 * strings, and adds it to the map at the centroid (lat/lon) specified for this
		 * polygon.
		 * @param title is the title (first) line of text for the marker.
		 * @param subTitle is the sub title (second) line of text for the marker.
		 */
        public function addMarker(title:String, subTitle:String):void
        {
        	marker = new PolylineMarker(title,subTitle,this,map);
        	marker.visible = this.visible;
			this.map.putMarker(centroid,marker);
			marker.redraw();
        }
                
		/** 
		 * Sets the visibility of the overlay and its marker.
		 * Note: visibility is not the same as the visible property --
		 * visible may be temporarily set to false, for smoother redraw on zooming,
		 * while visibility denotes the user-selected visual state of the polygon
		 * (used for restoring visible polygons after a hide-on-zoom).
		 */
        override public function set visibility(vis:Boolean):void
        {
        	super.visibility = vis;
        	visible = vis;
        	if (marker) 
        	{
        		marker.visible = vis;
        		marker.redraw();
        	}
        }
        
		/**
		 * Make the overlay clickable or non-clickable. When clickable, the overlay
		 * listens for mouse events, and dispatches an OverlayEvent.OVERLAY_CLICKED event
		 * when the mouse is clicked within the overlay.
		 * @param setting is a flag to set clickable on (true) or off (false).
		 */
		public function set clickable(setting:Boolean):void
		{
			if (setting==true) 
			{
				addEventListener(MouseEvent.CLICK, onOverlayClicked);
 				addEventListener(MouseEvent.MOUSE_UP, onMouseAction);
 				addEventListener(MouseEvent.MOUSE_DOWN,onMouseAction);
 			} else {
				removeEventListener(MouseEvent.CLICK, onOverlayClicked);
 				removeEventListener(MouseEvent.MOUSE_UP, onMouseAction);
 				removeEventListener(MouseEvent.MOUSE_DOWN,onMouseAction);
 			} 
			mouseEnabled = setting;
							
			// on MOUSE_DOWN, save the mouse x,y position for comparison on MOUSE_CLICK;
			// on MOUSE_DOWN and MOUSE_UP, re-fire the event at the map's tile grid well,
			// so it can pan the map.
			function onMouseAction(evt:MouseEvent):void
			{
				if (evt.type == MouseEvent.MOUSE_DOWN) 
				{
					startMouseX = evt.localX;
					startMouseY = evt.localY;
				}
				map.grid.well.dispatchEvent(evt);
			}
		}

		// dispatch the OVERLAY_CLICKED event, if the mouse-click includes a move that is
		// within the preset tolerance distance (otherwise, it is considered a drag, not a click).
		private function onOverlayClicked(evt:MouseEvent):void
		{
			var diff:int = Math.abs(evt.localX - startMouseX) + Math.abs(evt.localY - startMouseY);
			if (diff <= CLICK_TOLERANCE) 
				dispatchEvent(new OverlayEvent(this, OverlayEvent.OVERLAY_CLICKED));
		}

		/**
		 * Get the bounding rectangle of the polygon in stage coordinates.
		 * @return a Rectangle that describes the bounding box of this polygon in stage coordinates.
		 */
 		public function get stageRect():Rectangle
		{
			// convert from local to stage coordinates by adding polygon's x and y values to 
			// the stage position of the container (polyCont).
  			_stageRect.x = polyCont.x + left;
 			_stageRect.y = polyCont.y + top;
 			_stageRect.width = right-left;
			_stageRect.height = bottom-top;
 			return _stageRect;
		}

		/**
		 * Get the bounding rectangle of the portion of the polygon that is visible on stage.
		 * Used by PolylineMarker to update marker size and position when polygon is partially
		 * visible on screen.
		 * @return a Rectangle that describes the bounding box of this polygon's visible portion
		 * in stage coordinates. 
		 */
 		public function get visibleRect():Rectangle
		{
			return map.getBounds(map.mask).intersection(stageRect);
		}
 		
 		/**
 		 * Redraw the polygon after an update.
 		 */
        override public function redraw(sprite:Sprite):void
        {
            sprite.graphics.clear();
            // set the line style for this sprite
            sprite.graphics.lineStyle(lineStyle.thickness, lineStyle.color, 0.0, lineStyle.pixelHinting, lineStyle.scaleMode,lineStyle.caps, lineStyle.joints,lineStyle.miterLimit);

			// set flag for pen up/down selection
			var startedDrawing:Boolean = false;
			
			sprite.graphics.beginFill(fillColor, fillAlpha);
			
			// set vars to track bounds of polygon
			left = top = 10000;
			right = bottom = -10000;

			// draw the polygon
            for each (var l:Location in points) {
    	        var p:Point = map.locationPoint(l,sprite);
    	        
    	        if (!startedDrawing) {
	        	    sprite.graphics.moveTo(p.x, p.y);
    	        	startedDrawing = true;
    	        } else {
    	        	sprite.graphics.lineTo(p.x, p.y);
    	        }

				// store edge metrics of redraw region
    	        if (p.x < left) left = p.x;
    	        if (p.x > right) right = p.x;
    	        if (p.y < top) top = p.y;
    	        if (p.y > bottom) bottom = p.y;
            }
            sprite.graphics.endFill();

			// redraw the marker if present
            if (marker) {
            	marker.visible = visible;
	            marker.redraw();
            }	
        }                
    }
}
